-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Switchable buffering for Stdout #78515
Conversation
r? @dtolnay (rust_highfive has picked a reviewer for you, use r? to override) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
Some first comments:
If you can easily separate those, having that in a separate PR would be nice.
I'd say so, yes, absolutely. |
A lot can happen with streams. A pipe can be opened, redirected, closed, et cetera. The ability to respond to such a dynamic environment is a must. We should offer an interface morally equivalent to https://linux.die.net/man/3/setvbuf (which is a C standard, so it should apply cross-OS), while also being mindful that an author may want to manage their interactions in response to user-space commands like https://www.gnu.org/software/coreutils/manual/html_node/stdbuf-invocation.html or similar. And if possible we would like to write to terminals using line buffering by default and write to non-terminals using block buffering by default, following the convention of most environments. That doesn't have to go in this PR, but we may wish to open a follow up issue. I am given to understand that Microsoft Windows has interesting nuances to be aware of that don't mesh with a Unix-dominant view of how things should go, in particular in its cmd.exe prompt and its NTFS and ReFS file systems, so it would be a good idea to eventually collect input from someone who was well-versed in those before stabilizing this, presuming it lands. Though I am also given to understand the recent Windows Terminal more closely reflects interfaces in other OS: https://github.com/Microsoft/Terminal |
Yeah, for sure; this is why I wanted to get the "switchable" behavior approved & merged before setting out to actually start picking default behaviors. This way we can add platform-specific logic in separate PRs, each with feedback from experts in the respective platforms. |
Done. |
As it happens, I actually have some indirect experience here; we encountered some odd behavior where console system calls were failing in alacrity and Windows Terminal but not in cmd. We were never able to identify a root cause, since the relevant system call doesn't exhaustively document the conditions under which it fails, but my best guess right now is it has to do with the Alacritty and Windows Terminal interacting with ANSI command codes in a way that cmd does not:
|
I'm wondering if it might make sense to not add a new type ( (And (It'd also save some effort in having to come up with a clear name for |
I agree that it'd be nice to have the space savings, but I disagree that this functionality should be collapsed into
|
☔ The latest upstream changes (presumably #77801) made this pull request unmergeable. Please resolve the merge conflicts. Note that reviewers usually do not review pull requests until merge conflicts are resolved! Once you resolve the conflicts, you should change the labels applied by bors to indicate that your PR is ready for review. Post this as a comment to change the labels:
|
@Lucretiel any updates on this? |
@Lucretiel you'll need another rebase ;) |
And after rebasing you should do (at)rustbot ready to update the status so it has a chance to get a review before it bitrots again. |
Will do, thank you for the advice |
@Lucretiel any updates? |
Sorry for more delays. Working on it tonight |
dc7c342
to
1e564bc
Compare
1e564bc
to
a69dca8
Compare
Rebase finished; conflicts resolved. |
@rustbot ready |
☔ The latest upstream changes (presumably #98242) made this pull request unmergeable. Please resolve the merge conflicts. |
This is still stuck on an API design question: #78515 (comment) Nominated for libs-api discussion. |
We've discussed this pull request in the recent libs-api meeting. This PR seems to be stuck because it's too broad, making it hard to focus on a single decision. It would probably be a lot easier to make progress here if it was split into parts that each can be decided separately. Here's a few thoughts that came up in the meeting:
To make progress on this, it's probably best to close this PR for now and first get consensus on the API surface, before discussing any implementation. It's not clear whether See https://std-dev-guide.rust-lang.org/feature-lifecycle/api-change-proposals.html for how to propose an unstable API, focussing on just the interface without the implementation details. It's advisable to propose |
@Lucretiel any updates on this? |
#78515 (comment) has guidance on how to make progress on this use case, including a suggestion that "it's probably best to close this PR". I'll do that now since it isn't something we would be able to accept in this form, but the library API team still has a desire to address the use case that motivated this PR, and we'd be happy to see that ACP and the rest of the design conveyed in Mara's comment. |
I remain staunchly opposed to this for 0-cost abstraction reasons but I'm happy to be overruled.
Noted, I'll work on a more formal proposal
For sure; my intention with this PR as written was that |
@Lucretiel I created a vaguely related proposal: rust-lang/libs-team#148 |
Summary
This PR adds "switchable" buffering to Stdout. Instead of being unconditionally line-buffered, stdout can now be put in one of 3 modes: line buffering, which is the current behavior; block buffering, which mirrors
BufWriter
; and unbuffered, which (with one small exception, see Design Notes) forwards all writes directly to the underlying device.This PR additionally contains some proposed implementation updates toMoved to #78551BufWriter
, with a focus on reducing total devicewrite
calls in the average case. These updates are based on lessons learned from the design ofLineWriterShim
, as well as some discussions with @workingjubilee on this topic. This could be considered "out of scope" for this PR, so at the discretion of the reviewers I'm happy to move it to a separate PR, but I was "in the neighborhood", so to speak. See Design Notes for details about these changes.This PR moves us another step closer to #60673, which calls for
stdout
to use block buffering when appropriate (ie, when stdout is a not a tty). This PR does not change any current startup behavior;stdout
is still unconditionally line buffered by default. Instead, this PR creates only an implementation of switchable buffering (which lives in theSwitchWriter
type), which future PRs (probably individual ones for different platforms) can use to select an appropriate startup default for their launch environment.Side note on terminology
Throughout this proposal I'll be referring to writers flushing / buffers being flushed; unless I specify otherwise, I always mean that the data is being flushed one level, to the wrapped
io::Write
writer. "Full" flushing (ie, forwarding all the way though the stack to the underlying sink) is assumed to only happen on an explicit user call toflush
.Design notes
SwitchWriter
The core of this PR is the
SwitchWriter
type. It's fairly simple; it has a current mode, and delegates all write operations to itsBufWriter
as follows:BufWriter
.LineWriterShim
(a wrapper around&mut BufWriter
, implementing line buffering logic) and forwards to it. Treatment of existing buffered data is entirely deferred toLineWriterShim
.BufWriter
is flushed, then forwards directly to theio::Write
contained by theBufWriter
.write_fmt
tends to be a lot of very short writes, it is buffered into theBufWriter
and immediately flushed. The write still completes immediately, but we assume in practice that a caller ofwrite_fmt
on an unbufferedSwitchWriter
doesn't care if every individual write that entails is forwarded directly to the writer, as much as they care that the write is forwarded immediately.Changing the buffering mode never performs any synchronous i/o operations; it only changes the behavior of subsequent writes. The treatment of buffered content during a mode shift is left deliberately unspecified; in practice it's treated by whatever the behavior of the current mode is. For instance, when switching from block buffering to line buffering, any previously buffered content will (usually) remain buffered until a new line is written by the user.
SwitchWriter
does not retroactively scan the buffer for newlines to flush after such a mode switch. Conversely, it's possible (though unlikely) for partial writes from the underlying device to cause lines in line buffering mode to be buffered without being flushed.LineWriter
is designed to retry these writes ASAP (on subsequent write calls), but switching to block buffered mode will cause this content to remain buffered. It is assumed that, in cases where precision is needed, the user wouldflush
before switching modes.Strictly speaking, it isn't necessary for the fulfillment of #60673 for
stdout
to be switchable, only that the mode is set based on some environmental factors at startup. In practice, however, this means that the implementation needs to be able to switch over the current mode, so it's a trivial addition to make it further switchable at runtime. This has the side benefit of making it easy to break #60673 into different parts; without this public interface, the PR fails the "unused code" checks, and would require an implementation ofisatty
to be added immediately. Because this new API is unstable, I figure we can always remove it after #60673 is complete if we find it isn't justifying its inclusion.stdout
interface changesThis change adds
buffer_mode
andset_buffer_mode
toStdoutLock
, which cause the buffering mode to be globally switched. There's not much else to talk about here. CurrentlySwitchWriter
is not exported publicly (though it could be someday), so this API is the only way right now for any of this behavior to be accessed.Follow up items
The functional aspects of this PR are complete, but there are still several open tasks that need to be completed. Some are code related (write comprehensive tests & docs), which others are publishing related (this PR adds new public methods to
StdoutLock
, which need to be discussed and approved).Tasks
SwitchWriter
testsstdout
switching testsstdout
tested specifically as a write target? I see tests for panic consistency / recovery, and extensive tests forBufWriter
,LineWriter
, etc., but didn't immediately see tests for "writing to stdout".SwitchWriter
; even though it's not exported publicly, we still want it to be documented for developers reading it.Open questions
While I've made assumptions about these, and described my rationale as much as possible above, I wanted to call out the specific things that I wanted to be sure are addressed in review.
Stdout
, as well asStdoutLock
?Next steps
Assuming this PR gets merged (or, at least, the
SwitchWriter
part), the only remaining step is to actually create an interface for the platform to report its preferred stdout configuration based on the startup environment. This would be nearly identical to the waystdout_raw
oris_ebadf
are handled today: a standard interface is implemented in the varioussys
crates and consumed via a uniform interface inio
. My envisioned interface looks like:This will, of course, be discussed in more detail in that future PR.